<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="description" content="这是定制给您的拼图游戏~ 快来玩吧~">
    <link rel="stylesheet" href="./assets/global.css">
    <title>拼图游戏</title>
    <style>
        body {
            user-select: none;
            overflow: hidden;
            --tm-color: rgb(28, 139, 241);
        }

        .jigsaw {
            position: relative;
            height: var(--height);
            width: var(--width);
            user-select: none;
        }

        .jigsaw .jigsaw-item {
            position: absolute;
            background-color: #fff;
            --m-width: calc(var(--width) / var(--row));
            --m-height: calc(var(--height) / var(--line));
            width: var(--m-width);
            height: var(--m-height);
            left: calc(var(--x) * var(--m-width));
            top: calc(var(--y) * var(--m-height));
            background-size: var(--width) var(--height);
            background-position: calc((var(--row) - var(--ox)) * var(--m-width)) calc((var(--line) - var(--oy)) * var(--m-height));
            transition: all .5s;
            box-sizing: border-box;
            border: 2px solid #fff;
        }

        .jigsaw .gameover {
            position: absolute;
            left: 0;
            right: 0;
            top: 0;
            bottom: 0;
            background: rgba(255, 255, 255, 0.6);
            color: rgba(0, 0, 0, 0.6);
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 20px;
            display: none;
            z-index: 90;
            transition: all .5s;
            opacity: 0;
        }

        .jigsaw .surprise {
            position: fixed;
            left: 0;
            right: 0;
            top: 0;
            bottom: 0;
            background-color: rgba(255, 255, 255, 0.6);
            color: rgba(0, 0, 0, 0.6);
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 20px;
            display: none;
            z-index: 100;
            transition: all .2s;
            opacity: 0;
            background-repeat: no-repeat;
            object-fit: cover;
            background-size: contain;
            background-position: center center;

        }

        .generate {
            display: none;
            align-items: center;
            flex-direction: column;
        }

        .generate .input {
            display: flex;
            margin: 20px 0 0 0;
            width: 80vw;
            align-items: center;
        }

        .generate .input .label {
            width: 100px;
            box-sizing: border-box;
            padding-right: 20px;
            text-align: end;
            font-size: 14px;
        }

        .generate .input input,
        .generate .input .upload {
            flex: 1;
        }

        .generate .input input[type='number'] {
            text-align: center;
            width: 34px;
            height: 34px;
            flex: none;
            outline: none;
            box-sizing: border-box;
            border: 1px solid #ddd;
        }

        .generate .input .upload {
            color: var(--tm-color);
            font-size: 14px;
        }

        .generate img[class*='input-'] {
            width: 80vw;
            box-sizing: border-box;
            height: 200px;
            padding-left: 100px;
            margin-top: 10px;
            object-fit: cover;
        }

        .generate img:not([src]) {
            display: none;
        }

        .generate .submit {
            margin-top: 20px;
            width: 200px;
            height: 40px;
            background-color: #fff;
            color: var(--tm-color);
            border: 1px solid var(--tm-color);
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 15px;
            transition: all .5s;
        }

        .generate .submit:hover {
            background-color: var(--tm-color);
            color: white;
            border: 1px solid transparent;
        }
    </style>
</head>

<body>
    <div class="jigsaw">
        <div class="gameover"></div>
        <div class="surprise"></div>
    </div>

    <div class="generate">
        <h2>定制一个惊喜吧!</h2>
        <div class="input line">
            <div class="label">行:</div>
            <input type="number" step="1" max="100">
        </div>
        <div class="input row">
            <div class="label">列:</div>
            <input type="number">
        </div>
        <div class="input image">
            <div class="label">图片:</div>
            <div class="upload">点击上传</div>
        </div>

        <img class="input-image">

        <div class="input surprise">
            <div class="label">惊喜:</div>
            <div class="upload">点击上传</div>
        </div>

        <img class="input-surprise">

        <div class="submit">提交</div>
    </div>

    <script>
        // 使用类封装代码
        class JigsawItem {
            ox = 0
            oy = 0
            x = 0
            y = 0

            left = null
            top = null
            zIndex = null;
            image = "";
            opacity = null;
            dom = null;
            moving = null;

            get style() {
                let temp = `--x:${this.x};`
                temp += `--y:${this.y};`
                temp += `--ox:${this.ox};`
                temp += `--oy:${this.oy};`
                temp += `background-image:url(${this.image});`
                if (this.left) temp += `left:${this.left}px;`
                if (this.top) temp += `top:${this.top}px;`
                if (this.zIndex) temp += `z-index:${this.zIndex};`
                if (this.opacity) temp += `opacity:${this.opacity};`
                if (this.moving) temp += `transition:none;`
                return temp;
            }
        }
        // 加解密
        class Base64 {
            // private property
            static keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
            // public method for encoding
            /**
             * 加密
             */
            static encode(input) {
                var output = "";
                var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
                var i = 0;
                input = this.#_utf8_encode(input);
                while (i < input.length) {
                    chr1 = input.charCodeAt(i++);
                    chr2 = input.charCodeAt(i++);
                    chr3 = input.charCodeAt(i++);
                    enc1 = chr1 >> 2;
                    enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
                    enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
                    enc4 = chr3 & 63;
                    if (isNaN(chr2)) {
                        enc3 = enc4 = 64;
                    } else if (isNaN(chr3)) {
                        enc4 = 64;
                    }
                    output = output +
                        this.keyStr.charAt(enc1) + this.keyStr.charAt(enc2) +
                        this.keyStr.charAt(enc3) + this.keyStr.charAt(enc4);
                }
                return output;
            }

            // public method for decoding
            /**
             * 解密
             */
            static decode(input) {
                var output = "";
                var chr1, chr2, chr3;
                var enc1, enc2, enc3, enc4;
                var i = 0;
                input = input?.replace(/[^A-Za-z0-9\+\/\=]/g, "");
                while (i < input.length) {
                    enc1 = this.keyStr.indexOf(input.charAt(i++));
                    enc2 = this.keyStr.indexOf(input.charAt(i++));
                    enc3 = this.keyStr.indexOf(input.charAt(i++));
                    enc4 = this.keyStr.indexOf(input.charAt(i++));
                    chr1 = (enc1 << 2) | (enc2 >> 4);
                    chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
                    chr3 = ((enc3 & 3) << 6) | enc4;
                    output = output + String.fromCharCode(chr1);
                    if (enc3 != 64) {
                        output = output + String.fromCharCode(chr2);
                    }
                    if (enc4 != 64) {
                        output = output + String.fromCharCode(chr3);
                    }
                }
                output = this.#_utf8_decode(output);
                return output;
            }

            // private method for UTF-8 encoding
            static #_utf8_encode(string) {
                string = string.replace(/\r\n/g, "\n");
                var utftext = "";
                for (var n = 0; n < string.length; n++) {
                    var c = string.charCodeAt(n);
                    if (c < 128) {
                        utftext += String.fromCharCode(c);
                    } else if ((c > 127) && (c < 2048)) {
                        utftext += String.fromCharCode((c >> 6) | 192);
                        utftext += String.fromCharCode((c & 63) | 128);
                    } else {
                        utftext += String.fromCharCode((c >> 12) | 224);
                        utftext += String.fromCharCode(((c >> 6) & 63) | 128);
                        utftext += String.fromCharCode((c & 63) | 128);
                    }

                }
                return utftext;
            }

            // private method for UTF-8 decoding
            static #_utf8_decode(utftext) {
                var string = "";
                var i = 0;
                var c = 0;
                var c1 = 0;
                var c2 = 0;
                while (i < utftext.length) {
                    c = utftext.charCodeAt(i);
                    if (c < 128) {
                        string += String.fromCharCode(c);
                        i++;
                    } else if ((c > 191) && (c < 224)) {
                        c2 = utftext.charCodeAt(i + 1);
                        string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
                        i += 2;
                    } else {
                        c2 = utftext.charCodeAt(i + 1);
                        c3 = utftext.charCodeAt(i + 2);
                        string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
                        i += 3;
                    }
                }
                return string;
            }
        }
        // 获取元素
        let jigsawDom = document.getElementsByClassName('jigsaw')?.[0];
        // 存储拼图信息
        let jigsawItems = [];
        // 游戏结束元素节点
        let gameoverDom = document.getElementsByClassName('gameover')?.[0];
        // 惊喜元素节点
        let surpriseDom = document.getElementsByClassName('surprise')?.[0];
        // 允许操作标识
        let allowOperate = true;
        // 移动的标识
        let moving = false;
        // 移动元素节点
        let movingDom = null;
        // 移动元素
        let movingItem = null;
        // 下面的元素
        let underItem = null;
        // 参数
        let params = { row: 3, line: 3, image: './assets/movie-poster/m-wmdaqxjlal.jpg', surprise: './assets/movie-poster/m-zljsj3.jpg' }
        // 随机位置的存储
        let randomPositionCache = {};
        // 随机位置
        function randomPos() {
            let x = parseInt(Math.random() * params.row + "")
            let y = parseInt(Math.random() * params.line + "")
            if (randomPositionCache[`${x}_${y}`]) return randomPos();
            randomPositionCache[`${x}_${y}`] = 1;
            return { x, y }
        }
        // 初始化拼图
        function initJigsaw() {
            jigsawDom.setAttribute('style', `--width:${params.width}px;--height:${params.height}px;--row:${params.row};--line:${params.line}`)
            for (let i = 0; i < params.row; i++) {
                for (let j = 0; j < params.line; j++) {
                    let jigsawItem = new JigsawItem();
                    let jigsawItemDom = document.createElement('div');
                    let tempPos = randomPos();
                    jigsawItem.ox = i;
                    jigsawItem.oy = j;
                    jigsawItem.x = tempPos.x;
                    jigsawItem.y = tempPos.y;
                    jigsawItem.image = params.image;
                    jigsawItem.dom = jigsawItemDom;
                    jigsawItemDom.setAttribute('class', 'jigsaw-item')
                    jigsawItemDom.setAttribute('style', jigsawItem.style)
                    jigsawItemDom.addEventListener('mousedown', mousedown)
                    jigsawItemDom.addEventListener('touchstart', mousedown)
                    jigsawDom.appendChild(jigsawItemDom);
                    jigsawItems.push(jigsawItem)
                }
            }
            document.addEventListener('mousemove', mousemove)
            document.addEventListener('touchmove', mousemove)
            document.addEventListener('mouseup', mouseup)
            document.addEventListener('touchend', mouseup)
        }
        // 下面的拼图
        function underJigsawItem(e) {
            let temp = null;
            for (let i = 0; i < jigsawItems.length; i++) {
                const item = jigsawItems[i];
                if (item == movingItem) continue;
                let offsetLeftWorld = item.dom.offsetLeft + jigsawDom.offsetLeft;
                let offsetWidth = item.dom.offsetWidth;
                let offsetTopWorld = item.dom.offsetTop + jigsawDom.offsetTop;
                let offsetHeight = item.dom.offsetHeight;
                if (offsetLeftWorld < e.x && e.x < offsetLeftWorld + offsetWidth && offsetTopWorld < e.y && e.y < offsetTopWorld + offsetHeight) {
                    item.opacity = .7
                    temp = item;
                }
                else item.opacity = 1;
                item.dom.setAttribute('style', item.style);
            }
            return temp;
        }
        // 更换位置
        function exchangeJigsawItem(self, target) {
            let tempItem = { ...target };
            target.x = self.x;
            target.y = self.y;
            target.zIndex = 1;
            self.moving = false;
            self.zIndex = 1;
            self.x = tempItem.x;
            self.y = tempItem.y;
            self.left = null;
            self.top = null;
            target.dom.setAttribute('style', target.style);
            self.dom.setAttribute('style', self.style);
            this.allowOperate = false;
            setTimeout(() => {
                this.allowOperate = true;
                restore(self)
                restore(target)
            }, 500);
        }
        // 恢复初始数据
        function restore(self) {
            self.left = null
            self.top = null
            self.zIndex = null;
            self.opacity = null;
            self.moving = null;
            self.dom.setAttribute('style', self.style);
        }
        // 检查游戏结束
        function checkOver() {
            if (jigsawItems.find(ji => ji.ox != ji.x || ji.oy != ji.y)) return;
            console.log("游戏结束");
            gameoverDom.textContent = "游戏胜利!";
            gameoverDom.setAttribute("style", 'display:flex;opacity:1');
            surpriseDom.setAttribute("style", `display:flex;background-image:url(${params.surprise});`);
            setTimeout(() => {
                surpriseDom.setAttribute("style", `display:flex;opacity:1;background-image:url(${params.surprise});`);
            }, 1000);
        }
        // 设置拼图参数
        function setParams() {
            try {
                let target = JSON.parse(Base64.decode(window.location.search?.split('id=')?.[1] ?? "{}"));
                for (const key in target) {
                    params[key] = target[key]
                }
            } catch (error) {
                console.log("解析失败!?", error);
            }
            let img = document.createElement('img');
            img.setAttribute("src", params.image)
            img.onload = function (e) {
                // 最大宽度
                let maxWidth = Math.min(document.body.clientWidth, 400)

                params.width = maxWidth;
                params.height = maxWidth / img.naturalWidth * img.naturalHeight // 通过图片原始宽高来获取宽高 计算比例
                // 调用初始化拼图
                initJigsaw();
            }
        }
        // 鼠标/手指落下事件
        function mousedown(e) {
            if (!allowOperate) return;
            movingDom = e.target;
            movingItem = jigsawItems.find(a => a.dom == movingDom);
            moving = movingItem.moving = true;
            movingItem.zIndex = 1;
            movingDom.setAttribute('style', movingItem.style);
        }
        // 鼠标/手指移动事件
        function mousemove(e) {
            if (!moving) return;
            if (e?.touches?.length > 0) {
                e.x = e.touches[0].pageX
                e.y = e.touches[0].pageY
            }
            movingItem.left = e.x - movingDom.clientWidth / 2;
            movingItem.top = e.y - movingDom.clientHeight / 2;
            underItem = underJigsawItem(e)
            movingItem.opacity = .7;
            movingDom.setAttribute('style', movingItem.style)
        }
        // 鼠标/手指抬起事件
        function mouseup(e) {
            if (!movingItem) return;
            if (underItem) exchangeJigsawItem(movingItem, underItem);
            else exchangeJigsawItem(movingItem, movingItem)
            moving = false;
            movingDom = null;
            movingItem = null;
            underItem = null;
            checkOver();
        }

        // 上传文件的地址
        let address = `http://y.linyisonger.cn/`;
        // 自定义参数
        let customParams = {
            row: 3,
            line: 2,
            image: "",
            surprise: ""
        }

        // 生成容器元素
        let generateDom = document.querySelector('.generate');
        // 获取输入元素 
        let inputLineDom = document.querySelector('.input.line > input');
        let inputRowDom = document.querySelector('.input.row > input');
        let inputImageDom = document.querySelector('.input.image > .upload');
        let inputSurpriseDom = document.querySelector('.input.surprise > .upload');
        // 获取图片元素
        let imgImageDom = document.querySelector('.input-image');
        let imgSurpriseDom = document.querySelector('.input-surprise');
        // 获取提交事件
        let submitDom = document.querySelector('.submit');

        // 添加事件
        inputLineDom.addEventListener('change', this.lineChange)
        inputRowDom.addEventListener('change', this.rowChange)
        inputImageDom.addEventListener('click', this.imageClick)
        inputSurpriseDom.addEventListener('click', this.surpriseClick)
        submitDom.addEventListener('click', this.submitClick);

        // 事件回调
        function lineChange(e) {
            customParams.line = e.target.value;
        }
        function rowChange(e) {
            customParams.line = e.target.value;

        }
        function imageClick() {
            uploadImage().then((res) => {
                customParams.image = `${address}${res.filePath}`
                imgImageDom.setAttribute('src', customParams.image)
            })
        }
        function surpriseClick() {
            uploadImage().then((res) => {
                customParams.surprise = `${address}${res.filePath}`
                imgSurpriseDom.setAttribute('src', customParams.surprise)
            })
        }
        function uploadImage() {
            return new Promise((resolve) => {
                const fileInput = document.createElement('input');
                fileInput.setAttribute('type', 'file');
                fileInput.setAttribute('accept', 'image/*');
                fileInput.addEventListener('change', () => {
                    const file = fileInput.files[0];
                    const reader = new FileReader();
                    reader.readAsDataURL(file);
                    reader.onload = function () {
                        if (file.size / 1024 / 1024 > 80) {
                            console.log('文件大小不超过80Mb');
                            alert('文件大小不超过80Mb')
                            return;
                        }
                        const data = new FormData();
                        data.append('file', file);
                        let r = new XMLHttpRequest();
                        r.open("post", `${address}file/upload`);
                        r.onloadend = () => {
                            resolve(JSON.parse(r.responseText))
                        }
                        r.send(data);
                    };
                });
                fileInput.click();
            })
        }
        // 提交跳转
        function submitClick() {
            if (!customParams.line) {
                alert("行不能为空!")
                return;
            }
            if (!customParams.row) {
                alert("列不能为空!")
                return
            }
            if (!customParams.image) {
                alert("请上传图片!")
                return
            }
            if (!customParams.surprise) {
                alert("请上传惊喜图片~")
                return
            }
            let id = Base64.encode(JSON.stringify(customParams))
            location.href = `${location.pathname}?id=${id}`
        }

        // 加载
        function load() {
            if (location.search.includes('?generate')) {
                // 初始样式变更
                generateDom.setAttribute("style", `display:flex`)
                document.body.setAttribute('style', `overflow-y: auto;`)
                // 初始值赋值
                inputLineDom.value = customParams.line;
                inputRowDom.value = customParams.row;
            }
            else {
                // 调用设置拼图参数
                setParams();
            }
        }
        load();
    </script>
</body>

</html>